/*
 * ConstructedFunctionObject.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import java.util.Vector;

import org.scriptme.ecmascript.ast.ASTStatementList;
import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.exceptions.ProgrammingError;
import org.scriptme.ecmascript.interpreter.EvaluationSource;
import org.scriptme.ecmascript.interpreter.Evaluator;
import org.scriptme.ecmascript.interpreter.ScopeChain;

// TODO: Auto-generated Javadoc
/**
 * Implements functions constructed from source text.
 */
public class ConstructedFunctionObject extends FunctionPrototype {

	/** The Constant PROTOTYPEstring. */
	private static final String PROTOTYPEstring = new String("prototype")
			.intern();

	/** The Constant PROTOTYPEhash. */
	private static final int PROTOTYPEhash = PROTOTYPEstring.hashCode();

	/** The function ast. */
	private ASTStatementList theFunctionAST;

	/** The arguments. */
	private String[] theArguments;

	/** The local variable names. */
	private Vector localVariableNames;

	/** The evaluation source. */
	private EvaluationSource evaluationSource = null;

	/** The function source. */
	private String functionSource = null;

	/** The current arguments. */
	private ESValue currentArguments = ESNull.theNull;

	/**
	 * Instantiates a new constructed function object.
	 * 
	 * @param functionPrototype
	 *            the function prototype
	 * @param evaluator
	 *            the evaluator
	 * @param functionName
	 *            the function name
	 * @param evaluationSource
	 *            the evaluation source
	 * @param functionSource
	 *            the function source
	 * @param arguments
	 *            the arguments
	 * @param localVariableNames
	 *            the local variable names
	 * @param aFunctionAST
	 *            the a function ast
	 */
	private ConstructedFunctionObject(FunctionPrototype functionPrototype,
			Evaluator evaluator, String functionName,
			EvaluationSource evaluationSource, String functionSource,
			String[] arguments, Vector localVariableNames,
			ASTStatementList aFunctionAST) {
		super(functionPrototype, evaluator, functionName, arguments.length);
		this.evaluationSource = evaluationSource;
		this.functionSource = functionSource;
		theFunctionAST = aFunctionAST;
		theArguments = arguments;
		this.localVariableNames = localVariableNames;
	}

	/**
	 * get the string defining the function.
	 * 
	 * @return the source string
	 */
	public String getFunctionImplementationString() {
		if (functionSource == null) {
			StringBuffer str = new StringBuffer();
			str.append("function ");
			str.append(getFunctionName());
			str.append("(");
			for (int i = 0; i < theArguments.length; i++) {
				if (i > 0)
					str.append(",");
				str.append(theArguments[i]);
			}
			str.append(")");
			str
					.append("function {<internal abstract syntax tree representation>}");
			return str.toString();
		} else {
			return functionSource;
		}
	}

	/**
	 * Get the list of local variables of the function as a vector.
	 * 
	 * @return the Vector of local variable name strings
	 */
	public Vector getLocalVariableNames() {
		return localVariableNames;
	}

	/**
	 * Get the function parameter description as a string.
	 * 
	 * @return the function parameter string as (a,b,c)
	 */
	public String getFunctionParametersString() {
		StringBuffer str = new StringBuffer();
		str.append("(");
		for (int i = 0; i < theArguments.length; i++) {
			if (i > 0)
				str.append(",");
			str.append(theArguments[i]);
		}
		str.append(")");
		return str.toString();
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#callFunction(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		ESValue value = null;
		ESArguments args = ESArguments.makeNewESArguments(evaluator, this,
				theArguments, arguments);
		ESValue oldArguments = currentArguments;
		currentArguments = args;
		try {
			value = evaluator.evaluateFunction(theFunctionAST,
					evaluationSource, args, localVariableNames, thisObject);
		} finally {
			currentArguments = oldArguments;
		}
		return value;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doConstruct(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		ESValue prototype = getProperty(PROTOTYPEstring, PROTOTYPEhash);
		ESObject op = evaluator.getObjectPrototype();
		if (!(prototype instanceof ESObject))
			prototype = op;
		ESObject obj = new ObjectPrototype((ESObject) prototype, evaluator);
		ESValue result = callFunction(obj, arguments);
		// The next line was probably a misinterpretation of // 15.3.2.1 (18)
		// which returned an other object if the function returned an object
		// if (result instanceof ESObject) obj = (ESObject) result;
		return obj;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#toString()
	 */
	public String toString() {
		return getFunctionImplementationString();
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#toDetailString()
	 */
	public String toDetailString() {
		StringBuffer str = new StringBuffer();
		str.append("<Function: ");
		str.append(getFunctionName());
		str.append("(");
		for (int i = 0; i < theArguments.length; i++) {
			if (i > 0)
				str.append(",");
			str.append(theArguments[i]);
		}
		str.append(")>");
		return str.toString();
	}

	/**
	 * Utility function to create a function object. Used by the EcmaScript
	 * Function function to create new functions
	 * 
	 * @param evaluator
	 *            the Evaluator
	 * @param functionName
	 *            the name of the new function
	 * @param evaluationSource
	 *            An identification of the source of the function
	 * @param sourceString
	 *            The source of the parsed function
	 * @param arguments
	 *            The array of arguments
	 * @param localVariableNames
	 *            the list of local variable declared by var
	 * @param aFunctionAST
	 *            the parsed function
	 * 
	 * @return A new function object
	 */
	public static ConstructedFunctionObject makeNewConstructedFunction(
			Evaluator evaluator, String functionName,
			EvaluationSource evaluationSource, String sourceString,
			String[] arguments, Vector localVariableNames,
			ASTStatementList aFunctionAST) {
		ConstructedFunctionObject theNewFunction = null;
		try {
			FunctionPrototype fp = (FunctionPrototype) evaluator
					.getFunctionPrototype();

			theNewFunction = new ConstructedFunctionObject(fp, evaluator,
					functionName, evaluationSource, sourceString, arguments,
					localVariableNames, aFunctionAST);
			ObjectPrototype thePrototype = ObjectObject.createObject(evaluator);
			theNewFunction.putHiddenProperty("prototype", thePrototype);
			thePrototype.putHiddenProperty("constructor", theNewFunction);
		} catch (EcmaScriptException e) {
			e.printStackTrace();
			throw new ProgrammingError(e.getMessage());
		}
		return theNewFunction;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#getPropertyInScope(java.lang.String,
	 *      org.scriptme.ecmascript.interpreter.ScopeChain, int)
	 */
	public ESValue getPropertyInScope(String propertyName,
			ScopeChain previousScope, int hash) throws EcmaScriptException {
		if (propertyName.equals("arguments")) {
			return currentArguments;
		} else {
			return super.getPropertyInScope(propertyName, previousScope, hash);
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#getProperty(java.lang.String,
	 *      int)
	 */
	public ESValue getProperty(String propertyName, int hash)
			throws EcmaScriptException {
		if (propertyName.equals("arguments")) {
			return currentArguments;
		} else {
			return super.getProperty(propertyName, hash);
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#hasProperty(java.lang.String,
	 *      int)
	 */
	public boolean hasProperty(String propertyName, int hash)
			throws EcmaScriptException {
		if (propertyName.equals("arguments")) {
			return true;
		} else {
			return super.hasProperty(propertyName, hash);
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#putProperty(java.lang.String,
	 *      org.scriptme.ecmascript.data.ESValue, int)
	 */
	public void putProperty(String propertyName, ESValue propertyValue, int hash)
			throws EcmaScriptException {
		if (!propertyName.equals("arguments")) {
			super.putProperty(propertyName, propertyValue, hash);
		} // Allowed via putHiddenProperty, used internally !
	}
}
